package com.jintian.smart.kernel.module.config;

import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.module.Model;
import com.jintian.smart.kernel.module.impl.ModelImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * 模块加载器，Ordered.LOWEST_PRECEDENCE 最后执行
 */
@Slf4j
public class ModelLoader implements WebMvcConfigurer, BeanDefinitionRegistryPostProcessor, ApplicationContextAware, Ordered, SmartLifecycle {
    private ApplicationContext applicationContext;

    private String modelPath;

    private Map<String, Model> modelMap = new ConcurrentHashMap<>();

    private AtomicBoolean initialized = new AtomicBoolean(false);

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private void loadModel(BeanDefinitionRegistry registry) {
        if (modelPath == null) {
            modelPath = System.getProperty("APP_HOME") + "/model";
        }
        @SuppressWarnings("unchecked")
        List<String> modelProject = Binder.get(this.applicationContext.getEnvironment())
                .bind("smart.model.project", List.class).orElse(Collections.emptyList());
        Path root = Paths.get(modelPath);
        if (root.toFile().exists())
            try {
                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        if (dir.getParent().equals(root) || root.endsWith(dir))
                            return FileVisitResult.CONTINUE;
                        return FileVisitResult.SKIP_SUBTREE;
                    }

                    @Override
                    public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                        if (path.getFileName().endsWith("model.xml")) {
                            try (InputStream in = new FileInputStream(path.toFile())) {
                                Path p = path.getParent();
                                String name = p.getName(p.getNameCount() - 1).toString();
                                if (modelProject != null && modelProject.size() > 0 && !modelProject.contains(name)) {
                                    return FileVisitResult.CONTINUE;
                                }
                                try {
                                    log.info("======> 创建model");
                                    ModelImpl model = new ModelImpl();
                                    model.setName(name);
                                    model.setParentContext(applicationContext);
                                    model.load(path.toFile());
                                    modelMap.put(name, model);
                                    if (registry != null) {
                                        registerModel(registry, model);
                                        model.init();
                                    } else {
                                        model.init();
                                        model.start();
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        for (Model model : modelMap.values()) {
            Map<String, Resource[]> handlerLocations = model.getResourceHandlerLocations();
            for (Entry<String, Resource[]> e : handlerLocations.entrySet()) {
                registry.addResourceHandler(e.getKey()).addResourceLocations(e.getValue());
            }
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        loadModel(registry);
    }

    private void registerModel(BeanDefinitionRegistry registry, Model model) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Model.class, new Supplier<Model>() {
            @Override
            public Model get() {
                return model;
            }
        });
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition("model$" + model.getName(), builder.getBeanDefinition());
        // model.init();
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public void start() {
        List<String> info = new ArrayList<String>();
        Collection<RequestMappingHandlerMapping> mappings = applicationContext
                .getBeansOfType(RequestMappingHandlerMapping.class).values();
        for (RequestMappingHandlerMapping mapping : mappings) {
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
            for (RequestMappingInfo rmi : handlerMethods.keySet()) {
                HandlerMethod handlerMethod = handlerMethods.get(rmi);
                Set<String> patterns = rmi.getPatternValues();
                info.add(String.format("%s \t\t%s.%s", StringUtil.join(patterns, ","),
                        handlerMethod.getBeanType().getSimpleName(), handlerMethod.getMethod().getName()));
            }
        }
        Collections.sort(info);
        log.info("RequestMappingHandlerMapping:\n{}", StringUtil.join(info, "\n"));
        if (initialized.compareAndSet(false, true)) {
            for (Model m : modelMap.values()) {
                m.start();
            }
        }
    }

    @Override
    public void stop() {
        if (initialized.getAndSet(false)) {
            for (Model m : modelMap.values()) {
                m.stop();
            }
        }
    }

    @Override
    public boolean isRunning() {
        return initialized.get();
    }

    @Override
    public int getPhase() {
        // 优先于swagger的DocumentationPluginsBootstrapper
        return Integer.MAX_VALUE - 10;
    }
}
